#!/usr/bin/python
#
# -*- coding: utf-8 -*-
#*****************************************************************************
#*  OpenDomo: Security & Domotics
#*  Copyright (C) 2010  Daniel Lerch Hostalot <dlerch@gmail.com>
#*
#*  This program is free software: you can redistribute it and/or modify
#*  it under the terms of the GNU General Public License as published by
#*  the Free Software Foundation, either version 3 of the License, or
#*  (at your option) any later version.
#*
#*  This program is distributed in the hope that it will be useful,
#*  but WITHOUT ANY WARRANTY; without even the implied warranty of
#*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#*  GNU General Public License for more details.
#*
#*  You should have received a copy of the GNU General Public License
#*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#*****************************************************************************
# Author: Isidro Catalan <skarvin@gmail.com>
# Dependencies: twill (sudo easy_install twill)

# @file odapi.py
# @brief Handles the comunication with the agent's cgi module

# Libraries
import urllib
import urllib2
import cookielib
from time import strftime

from xml.dom import minidom
import os
import sys
import ConfigParser
#from twill import commands as twill

# Includes
from models import OpendomoResponseModel, CommandModel, ItemModel

# Constants
COOKIE_JAR="/tmp/cookies_od"
TMP_RESPONSE="/tmp/odapi_response.log"
ERROR_LOG="/tmp/odapi_error.log"
CONF_FILE=os.environ["HOME"]+"/odapi.conf"
MENU_ITEM=1
COMMAND=2
SCRIPT=3
CAMERA_LIST=4
CAMERA_SHOW=5
LOGGED_IN=False
TOKEN_LEN=41

## Config parser handler
config = ConfigParser.RawConfigParser()

## Class OpenDomoAPI documentation
#
#  It handles the comunication with the agent's cgi module using its xml 
#  interface.

class OpenDomoAPI():

	# Config file vars
	username = ""
	password = ""
	cgi_host = ""
	cgi_port = ""	
	use_ssl = "yes"
	
	# intern variables
	logged = False	
	use_xml = True
	# Error codes
	errno = 0

	## The constructor, it initializes the object with the
	# agent connection data 
	# @param self The object pointer.
	# @param cgi_host The agent IP address or domain name
	# @param cgi_port The agent port number
	# @param use_ssl  Use ssl?
	def __init__(self, cgi_host, cgi_port, use_ssl):
		self.cgi_host=cgi_host
		self.cgi_port=cgi_port
		self.use_ssl=use_ssl

	## It saves the current configuration.
	# @param self The object pointer.
	# @return Returns true if the configuration has been updated successfully
	def bSaveConfig(self):
		# Modify the configuration
		try:
			self.username=config.set('CGI Agent', 'username', username)		
			self.password=config.set('CGI Agent', 'password', password)		
			self.cgi_host=config.set('CGI Agent', 'cgi_host', cgi_host)		
			self.cgi_port=config.set('CGI Agent', 'cgi_port', cgi_port)					
			self.cgi_ssl=config.set('CGI Agent', 'use_ssl', use_ssl)		
			# Writing our configuration file
			with open(CONF_FILE, 'w') as configfile:
				config.write(configfile)
		except ConfigParser.NoOptionError:
			print 'No Option found'
			return False
			
		return True

	## It loads the configuration from the config file.
	# @param self The object pointer.
	# @return Returns true if the configuration has been read successfully
	def bReadConfig(self):
		try:
			config.read(CONF_FILE)
			self.username=config.get('CGI Agent', 'username')
			self.password=config.get('CGI Agent', 'password')
			self.cgi_host=config.get('CGI Agent', 'cgi_host')
			self.cgi_port=config.get('CGI Agent', 'cgi_port')			
			self.use_ssl=config.get('CGI Agent', 'use_ssl')				
		except ConfigParser.NoSectionError:
			print 'No section found while reading the config file'
			return False
		except ConfigParser.NoOptionError:			
			print 'No option found while reading the config file'
			return False
		
		return True

	## Retrieves the cgi root url so the script name or paramaters can
	#  be appended to it
    # @param self The object pointer.
	# @return Returns the cgi root url 
	def sGetCgiRoot(self):
		port = ""
		if self.cgi_port != "80":
			port = ":"+self.cgi_port

		if self.use_ssl == "yes":
			return "https://"+self.cgi_host+port+"/cgi-bin/od.cgi"
		elif self.use_ssl == "no":
			return "http://"+self.cgi_host+port+"/cgi-bin/od.cgi"
		
	## It finds the cgi agent by using multicast messages 	    
	# @param self The object pointer.
	# @return Returns the agent address running the cgi
	# @todo Needs to be implemented
	def sFindCgiAgent(self):
		# TODO: create a routine to discover the cgi through multicast
		return 0

	## Performs the login to the cgi agent overriding the credentials 
	#  in the config file											  	   
	#  @param self The object pointer.
	#  @param username The username.
   	#  @param password The password.
   	#  @return boolean It returns True if the login is successful
	def bLoginWithCredentials(self, username, password):
		self.username = username
		self.password = password
		return self.bLogin()

	## Performs the login to the cgi agent 
	#  in the config file											  	   
	#  @param self The object pointer.
   	#  @return boolean It returns True if the login is successful
	def bLogin(self):
		values = {'USER' : self.username, 'PASS' : self.password}
		self.oRequest("/",values)
		if self.errno == 0:
			self.logged = True
			return True
		else:
			return False

	## @desc It retrieves the session id from the cookie 
	#  @param self The object pointer.	
   	#  @return string String with the session ID	    	
	def sGetSessionId(self):
		str_auth = ''
		if os.path.exists(COOKIE_JAR):
			contents=file(COOKIE_JAR).read().replace('#','').replace('\n','').strip()
		if contents.find('HTSESSID') != -1:
			str_auth='HTSESSID='+contents.split('HTSESSID=')[1].split(';')[0].strip()

		return str_auth

	## It logs an error to the error file 
	#  @param self The object pointer.	
	#  @param description The error description		    
	def vLogError(self, description):
		time = strftime("%Y-%m-%d %H:%M:%S")
		f = open(ERROR_LOG, 'w')
		f.write(time+': '+ description+'\n')
		f.close()

	## Performs a request operation to the cgi 
	# @param self The object pointer.	
	# @param uri The url to be requested
	# @param post_data Post data to be added to the request operation
	# @return OpendomoResponseModel Model containing the information obtained after the request
	def oRequest(self, uri, post_data=None):
		model=OpendomoResponseModel()
		def response_read(response):
			# Parse the response and populate the model accordingly
			# DEBUG INFO
			f = open(TMP_RESPONSE, 'w')
			f.write(response)
			f.close()
			dom = minidom.parseString(response)
			rsp = dom.firstChild
			if rsp.nodeName != 'odcgi':
				model.gui_elements = []
				model.command_elements = []
			try:					
				if rsp.getElementsByTagName('error'):
					 # Process errors first!
					 errorlist = rsp.getElementsByTagName('error')
					 description = "Error: "
					 for error in errorlist:
						 model.error = True
						 if error.attributes.has_key('description'):
							 description += error.attributes['description'].nodeValue + ". "
						 else:
						 	 description += error.childNodes[0].data
						 	 
 					 	 model.error_description = description
					 raise ValueError(description)
				
				if len(rsp.getElementsByTagName('gui')) > 0 and rsp.getElementsByTagName('gui')[0].getElementsByTagName('item'):
					 # Process the menu item elements
					 itemlist = rsp.getElementsByTagName('gui')[0].getElementsByTagName('item')
					 for item in itemlist:
						  item_model = ItemModel()
						  item_model.label = item.attributes['label'].nodeValue
						  item_model.href = item.attributes['href'].nodeValue
						  model.gui_elements.append(item_model)		 
					# Processing images
				if len(rsp.getElementsByTagName('gui')) > 0 and rsp.getElementsByTagName('gui')[0].getElementsByTagName('img'):
					 # Process the menu item elements
					 itemlist = rsp.getElementsByTagName('gui')[0].getElementsByTagName('img')
					 for item in itemlist:
						  item_model = ItemModel()
						  item_model.label = item.attributes['alt'].nodeValue
						  item_model.href = item.attributes['src'].nodeValue
						  model.gui_elements.append(item_model)								
					 # Process the command items
				elif len(rsp.getElementsByTagName('gui')) > 0 and rsp.getElementsByTagName('gui')[0].getElementsByTagName('commands'):
					  commandslist = rsp.getElementsByTagName('gui')[0].getElementsByTagName('commands')	
					  for commands in commandslist:
							commandlist = commands.getElementsByTagName('command')
							for command in commandlist:
								 command_model = CommandModel()
								 command_model.id = commands.attributes["id"].nodeValue
								 command_model.label = command.attributes['label'].nodeValue
								 command_model.url = command.attributes['url'].nodeValue
								 command_model.state = command.attributes['state'].nodeValue						  
								 model.command_elements.append(command_model)  
				elif len(rsp.getElementsByTagName('gui')) > 0 and rsp.getElementsByTagName('gui')[0].getElementsByTagName('password'):
					  self.logged = False
				else:
					 # Another type of response, default to filling model.data
					 # with the raw XML response
					 model.data = response
				return model
				
			except IndexError, e:
				self.errno = 1
				self.vLogError('Failed to parse "%s".' % url)
				if hasattr(e, 'code'):
					self.vLogError('Failed with error code - %s.' % e.code)
				elif hasattr(e, 'reason'):
					self.vLogError('['+str(e.reason[0])+']'+' '+e.reason[1])
				return model
			# Error in script has been found
			except ValueError, e:
				self.errno = 1
				self.vLogError(model.error_description)
				return model
			except KeyError:
				self.errno = 1
				self.vLogError('Key not found while parsing xml')
				return model

		# Reset error code
		self.errno = 0
		url = self.sGetCgiRoot()+uri
		# Check if we are parsing XML
		if self.use_xml == True:
			url = url+"?GUI=XML"

		if(post_data != None):
			url_values = urllib.urlencode(post_data)
		else:
			url_values = None

		try:
			urlopen = urllib2.urlopen
			cj = cookielib.LWPCookieJar()				
			if cj != None:
				# now we have to install our CookieJar so that it is used as the default CookieProcessor in the default opener handler
			    if os.path.isfile(COOKIE_JAR):
			        cj.load(COOKIE_JAR)
			    if cookielib:
			        opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
			        urllib2.install_opener(opener)			
			        
			Request = urllib2.Request
			req = Request(url, url_values)
			response = urlopen(req)
			cj.save(COOKIE_JAR)			
		except IOError, e:
			self.vLogError('Failed to open "%s".' % url)
			if hasattr(e, 'code'):
				self.vLogError('Failed with error code - %s.' % e.code)
				self.errno = e.code
			elif hasattr(e, 'reason'):
				self.vLogError('['+str(e.reason[0])+']'+' '+e.reason[1])
			if self.errno == 0:
			   self.errno = 1
		else:
			data_res=response.read()
			model=response_read(data_res)
		
		return model
		
	## Returns an object containing the elements of the uri requested 
	# @param self The object pointer.		
	# @param uri The Uniform Resource Identifier
	# @return OpendomoResponseModel Model containing the information obtained after the request
	def aGetEntries(self, uri):
		
		model = self.oRequest(uri)
		entries=[]
	  
		for item_model in model.gui_elements:
			if item_model.href.endswith("browseFiles.sh"):
				#Hack to avoid <error description="Invalid path XML"/> error 
				# when calling the script browseFiles.sh?GUI=XML
				item_model.href=item_model.href+'?option[]=/'
			elif item_model.href.find("browseFiles.sh") != -1:
				item_model.href='/cgi-bin/od.cgi/tools/'+item_model.href
			elif item_model.href.startswith("browseCaptures.sh"):
				item_model.href='/cgi-bin/od.cgi/control/'+item_model.href
				
			element = [(item_model.label), True, item_model.href, MENU_ITEM]
			entries.append(element)
	  
		for command_model in model.command_elements:
			if command_model.state == 'off':
				element = [(command_model.id + ' ' + command_model.label), 
							 True, self.sGetCgiRoot()+'/control/'+command_model.url, 
							 COMMAND]
				entries.append(element)
	  
		return entries	

	## It returns True when the script has been executed succesfully 
	# @param self The object pointer.		
	# @param uri The Uniform Resource Identifier
   	# @return boolean It returns True if the login is successful
	def bExecute(self, uri):
		model = self.oRequest(uri)
		if model.error == True or self.errno != 0:
		   return False
		else:
		   return True


